package com.ruoci.redis.requestlimit.redis;

import com.ruoci.redis.constants.BusinessConstants;
import com.ruoci.redis.config.BusinessException;
import com.ruoci.redis.requestlimit.RequestUtils;
import com.ruoci.redis.requestlimit.redis.RequestLimit;
import com.ruoci.redis.response.ResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * @Author yang
 * 基于滑动窗口设计访问频率(存在边界值问题)
 **/
@Slf4j
@Aspect
@Component
public class RequestLimitAspect {

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 切点
     * @param requestLimit
     */
    @Pointcut("@annotation(requestLimit)")
    public void controllerAspect(RequestLimit requestLimit) {}

    @Around("controllerAspect(requestLimit)")
    public Object doAround(ProceedingJoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {
        long period = requestLimit.period();
        long limitCount = requestLimit.count();
        HttpServletRequest request = RequestUtils.getHttpServletRequest();
        String ip = RequestUtils.getIp(request);
        String uri = request.getRequestURI();
        String key = BusinessConstants.REQ_LIMIT.concat(uri).concat(ip);

        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        long currentMs = System.currentTimeMillis();
        // 将用户ip和uri组成key，timestamp为value，放入zSet
        zSetOperations.add(key, currentMs, currentMs);

        // 设置key的过期时间
        redisTemplate.expire(key, period, TimeUnit.SECONDS);

        // 0<=key<= 一分钟之前的数据
        zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);

        // 统计总数
        Long count = zSetOperations.zCard(key);

        if (count > limitCount) {
            log.error("接口拦截：{} 请求超过限制频率【{}次/{}s】,IP为{}", uri, limitCount, period, ip);
            throw new BusinessException(ResponseCode.REQUEST_LIMIT.getCode(),ResponseCode.REQUEST_LIMIT.getMessage());
        }
        return  joinPoint.proceed();
    }

}

